#include <dirent.h>
#include <rid.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#define STYLE_9
#include <fn85.h>
#include <rid_fn85.h>

#include <unfy/unfy.h>
#include <rls/rls.h>
#include <rls/rlsp.h>

#define NIT_SHORT_NAMES
#include <nit/list.h>
#include <nit/set.h>
#include <nit/map.h>
#include <nit/err.h>
#include <nit/file.h>
#include <nit/buf.h>
#include <nit/gap.h>

#include "utilize.h"
#include "examine.h"
#include "front.h"

#include "config.h"

/* add_________________ */
const Rid ilk_add_id = { 0x1f, 0x98, 0x8a, 0xb8, 0xfb, 0xe0, 0x0e, 0x10,
			 0xfb, 0xe0, 0x0e, 0x10, 0xfb, 0xe0, 0x0e, 0x10 };

/* id__________________ */
const Rid ilk_id_id = { 0x38, 0x84, 0x13, 0x2b, 0xfb, 0xe0, 0x0e, 0x10,
			0xfb, 0xe0, 0x0e, 0x10, 0xfb, 0xe0, 0x0e, 0x10 };

/* head________________ */
const Rid ilk_head_id = { 0x35, 0x69, 0x1f, 0x92, 0xfb, 0xe0, 0x0e, 0x10,
			  0xfb, 0xe0, 0x0e, 0x10, 0xfb, 0xe0, 0x0e, 0x10 };

/* Core code */

static Ilk_grp *
grp_get(Ilk_sys *sys, const Rid id, Rls_rule *rules)
{
	Ilk_grp *grp = sys->lib->old_grps;

	if (grp) {
		sys->lib->old_grps = (Ilk_grp *) grp->rules;
	} else if (!(grp = malloc(sizeof(*grp)))) {
		Rid_fn85 str;

		err_std(&sys->lib->err);
		rid_fn85(str, id);
		err_where(&sys->lib->err, str);
		return NULL;
	}

	rid_set(grp->id, id);
	grp->rules = rules;
	grp->changed = 0;
	grp->accessed = 1;
	return grp;
}

static void
grp_recycle(Ilk_sys *sys, Ilk_grp *grp)
{
	grp->rules = (Rls_rule *) sys->lib->old_grps;
	sys->lib->old_grps = grp;
}

Ilk_grp *
ilk_grp_load(Ilk_sys *sys, const Rid id, Unfy_stat *stat)
{
	Ilk_rule_path path;
	Ilk_grp *grp = grp_get(sys, id, NULL);

	if (!grp)
		goto err_no_mem;

	ilk_rule_path(path, id);

	switch (file_exists(&sys->dir, path, &sys->lib->err)) {
	case 0:
		if (stat) {
			*stat = UNFY_NO;
			grp_recycle(sys, grp);
			return NULL;
		}

		break;
	case 1:
		if (ilk_path_grp(grp, path, 1, sys) >= 0)
			break;

		/* fallthru */
	default:
		goto err_grp;
	}

	if (map_add(&sys->grps, id, sizeof(Rid), grp, &sys->lib->stack) < 0) {
		err_cust(&sys->lib->err, "Failed to add grp");
		goto err_grp;
	}

	return grp;
err_grp:
	grp_recycle(sys, grp);
err_no_mem:
	if (stat)
		*stat = UNFY_ERR;

	return NULL;
}

int
ilk_grp_unload(Ilk_grp *grp, Ilk_sys *sys)
{
	if (ilk_grp_save(grp, sys) < 0)
		return -1;

	ilk_grp_discard(grp, sys);
	return 0;
}

void
ilk_grp_discard(Ilk_grp *grp, Ilk_sys *sys)
{
	ilk_grp_dispose(grp, sys);
	map_remove(&sys->grps, grp->id, sizeof(grp->id), &sys->lib->stack);
	grp_recycle(sys, grp);
}

Ilk_grp *
ilk_grp_get(Ilk_sys *sys, const Rid id, Unfy_stat *stat)
{
	Ilk_grp *grp = map_get(&sys->grps, id, sizeof(Rid));

	if (!grp)
		grp = ilk_grp_load(sys, id, stat);

	if (grp)
		grp->accessed = 1;

	return grp;
}

static int
rule_name(const Unfy_term *term, Rid id)
{
	if (!term)
		return -1;

	switch (term->type) {
	case UNFY_CONST:
		rid_set(id, term->u.id);
		return 0;
	case UNFY_LIST:
		if (!term->u.list || term->u.list->term->type != UNFY_CONST)
			return -1;

		rid_set(id, term->u.list->term->u.id);
		return 0;
	default:
		break;
	}

	return -1;
}

Ilk_grp *
ilk_grp_get_term(Ilk_sys *sys, const Unfy_term *term, Unfy_stat *stat)
{
	Rid id;

	if (rule_name(term, id) < 0) {
		if (stat)
			*stat = UNFY_ERR;

		err_cust(&sys->lib->err, "Invalid rule name");
		return NULL;
	}

	return ilk_grp_get(sys, id, stat);
}

int
ilk_grp_save(Ilk_grp *grp, Ilk_sys *sys)
{
	size_t pos = 0;
	Ilk_rule_path path;
	Nit_file file;
	Nit_err tmp;

	if (!grp->changed)
		return 0;

	ilk_rule_path(path, grp->id);

	if (rlsp_rule_ser(grp->rules, &sys->lib->buf.bytes, &sys->lib->buf.size,
			  &pos, &sys->lib->srlz, sys) < 0) {
		err_cust(&sys->lib->err, "Failed to serialize rules");
		goto err_no_file;
	}

	if (dir_sub(&sys->dir, path, &sys->lib->err) < 0 ||
	    file_open(&file, &sys->dir, path, &sys->lib->err) < 0)
		goto err_no_file;

	if (file_write(&file, grp->id, sizeof(Rid), &sys->lib->err) < 0 ||
	    file_write(&file, sys->lib->buf.bytes, pos, &sys->lib->err) < 0)
		goto err_file;

	file_cutoff(&file);

	if(file_close(&file, &sys->lib->err) < 0)
		goto err_file;

	grp->changed = 0;
	return 0;
err_file:
	file_close(&file, &tmp);
err_no_file:
	err_where(&sys->lib->err, path);
	return -1;
}

int
ilk_rule_origin(Rls_rule *rule, Rls_srch **srch_ref,
		uint64_t srch_cnt, Ilk_sys *sys)
{
	Ilk_srch_path path;
	Rid_fn85 id_str;

	if (rule) {
		Ilk_more *more = rule->dat;

		/* a priori*/
		if (!more->srch_cnt) {
			*srch_ref = NULL;
			return 0;
		}

		if (!*srch_ref &&
		    ilk_srch_get(srch_ref, more->srch_cnt, sys) < 0)
			return -1;
	}

	for (; *srch_ref; rls_srch_next(srch_ref)) {
		Unfy_term *id;

		if (!rid_cmp((*srch_ref)->rule->id, ilk_add_id) &&
		    (!rule ||
		     ((id = unfy_bind_get((*srch_ref)->rbind, ilk_id_id)) &&
		      !rid_cmp(id->u.id, rule->id))))
			return 0;
	}

	if (!rule)
		return 0;

	err_cust(&sys->lib->err, "Srch file does not contain rule");
	ilk_srch_path(path, srch_cnt);
	err_where(&sys->lib->err, path);
	rid_fn85(id_str, rule->id);
	err_what(&sys->lib->err, id_str);
	return -1;
}

int
ilk_meta_init(Ilk_meta *meta, uint64_t srch_cnt, uint64_t deps_len)
{
	meta->srch_cnt = srch_cnt;
	return gap_init(&meta->deps, deps_len);
}

void
ilk_meta_dispose(Ilk_meta *meta)
{
	gap_dispose(&meta->deps);
}

void
ilk_meta_get(Ilk_meta *meta, Rls_srch **srch_ref, Ilk_sys *sys)
{
	Unfy_term *id;
	Unfy_term *head;

	ilk_rule_origin(NULL, srch_ref, meta->srch_cnt, sys);

	if (!*srch_ref)
		return;

	id = unfy_bind_get((*srch_ref)->rbind, ilk_id_id);
	rid_set(meta->id, id->u.id);
	head = unfy_bind_get((*srch_ref)->rbind, ilk_head_id);
	rule_name(head, meta->grp_id);
	meta->changed = 1;
}

int
ilk_meta_save(Ilk_sys *sys, Ilk_meta *meta)
{
	size_t pos = 0;
	size_t size = 0;
	unsigned char *buf = NULL;
	Ilk_meta_path path;
	Nit_file file;
	Nit_err tmp;

	if (!meta->changed)
		return 0;

	ilk_meta_path(path, meta->id);

	if (ilk_meta_ser(meta, &buf, &size, &pos) < 0) {
		err_cust(&sys->lib->err, "Failed to serialize rules");
		goto err_no_file;
	}

	if (dir_sub(&sys->dir, path, &sys->lib->err) < 0 ||
	    file_open(&file, &sys->dir, path, &sys->lib->err) < 0)
		goto err_no_file;

	if (file_set(&file, buf, pos, &sys->lib->err) < 0 ||
	    file_close(&file, &sys->lib->err) < 0)
		goto err_file;

	free(buf);
	meta->changed = 0;
	return 0;
err_file:
	file_close(&file, &tmp);
err_no_file:
	free(buf);
	err_where(&sys->lib->err, path);
	return -1;
}

int
ilk_meta_load(Ilk_meta *meta, const Rid id, Ilk_sys *sys)
{
	Ilk_rule_path path;

	ilk_meta_path(path, id);
	rid_set(meta->id, id);
	return ilk_path_meta(meta, path, 1, sys);
}

int
ilk_meta_dep(Ilk_meta *meta, uint64_t srch_cnt)
{
	gap_end(&meta->deps);
	return gap_write(&meta->deps, &srch_cnt, sizeof(srch_cnt));
}

static void
clean_grps(Ilk_sys *sys)
{
	Nit_iter iter;
	int more = 1;

	if (!sys->grps.entry_num)
		return;

	iter_init(&iter, &sys->grps);

	while (more) {
		Ilk_grp *grp = iter_val(&iter);

		more = iter_next(&iter);

		if (grp->accessed) {
			grp->accessed = 0;
			continue;
		}

		/* If we can't unload, it's no big deal. We'll do it later. */
		ilk_grp_unload(grp, sys);
	}
}

static void
clean_srchs(Ilk_sys *sys)
{
	Nit_iter iter;
	int more = 1;

	if (!sys->srchs.entry_num)
		return;

	iter_init(&iter, &sys->srchs);

	while (more) {
		Ilk_grp *grp = iter_val(&iter);

		more = iter_next(&iter);

		if (grp->accessed) {
			grp->accessed = 0;
			continue;
		}

		/* If we can't unload, it's no big deal. We'll do it later. */
		ilk_grp_unload(grp, sys);
	}
}

Unfy_stat
ilk_srch(Ilk_sys *sys, Rls_srch **srch_ref, Unfy_term *term,
	 int *limit, Rls_info *info)
{
	Unfy_stat stat;

	++sys->srch_cnt;
	stat = rls_srch_limit(srch_ref, term, NULL, limit,
			      sys->lib->bend, info, sys, &sys->lib->rec);

	switch (stat) {
	case UNFY_YES:
		if (!(sys->srch_cnt % GRP_CLEAN_CYCLE) && !sys->lib->testing)
			clean_grps(sys);

		if (!(sys->srch_cnt % SRCH_CLEAN_CYCLE) && !sys->lib->testing)
			clean_srchs(sys);

		break;
	case UNFY_ERR:
		/* Since rls doesn't use nit err */
		if (!sys->lib->err.occured)
			err_cust(&sys->lib->err, "Failed searching");

		/* fallthru */
	case UNFY_NO:
		--sys->srch_cnt;
		break;
	}

	return stat;
}

static int
srch_extract_meta(Rls_srch *srch, Ilk_sys *sys, uint64_t srch_cnt)
{
	Rls_srch **srch_ref = &srch;
	Ilk_meta meta;

	ilk_meta_init(&meta, srch_cnt, 0);

	while (*srch_ref) {
		ilk_meta_get(&meta, srch_ref, sys);

		if (!*srch_ref)
			break;

		if (ilk_meta_save(sys, &meta) < 0) {
			ilk_meta_dispose(&meta);
			return -1;
		}

		rls_srch_next(srch_ref);
	}

	ilk_meta_dispose(&meta);
	return 0;
}

static int
srch_update_meta(Rls_srch *srch, Ilk_sys *sys, uint64_t srch_cnt)
{
	Ilk_more *more;
	uint64_t tmp;

	if (!srch)
		return 0;

	more  = srch->rule->dat;
	tmp = more->srch_cnt;

	/* tmp == 0 => a priori, it has no meta file */
	if (tmp != srch_cnt && tmp) {
		Ilk_meta_path path;

		ilk_meta_path(path, srch->rule->id);

		if (ilk_path_meta_dep(sys, path, srch_cnt) < 0)
			return -1;
	}

	more->srch_cnt = srch_cnt;
	rls_srch_next(&srch);

	if (srch_update_meta(srch, sys, srch_cnt) < 0) {
		more->srch_cnt = tmp;
		return -1;
	}

	more->srch_cnt = tmp;
	return 0;
}

int
ilk_srch_save(Ilk_sys *sys, Rls_srch *srch, uint64_t srch_cnt)
{
	size_t pos = 0;
	Ilk_srch_path path;
	Nit_file file;
	Nit_err tmp;

	if (srch_extract_meta(srch, sys, srch_cnt) < 0)
		return -1;

	if (srch_update_meta(srch, sys, srch_cnt) < 0)
		return -1;

	if (rlsp_srch_ser(srch, &sys->lib->buf.bytes, &sys->lib->buf.size, &pos)) {
		err_cust(&sys->lib->err, "Failed to serialize srch");
		return -1;
	}

	ilk_srch_path(path, srch_cnt);

	switch (file_exists(&sys->dir, path, &sys->lib->err)) {
	case 0:
		break;
	case 1:
		err_cust(&sys->lib->err, "Srch file already exists");
		/* fallthru */
	default:
		goto err_no_file;
	}

	if (dir_sub(&sys->dir, path, &sys->lib->err) < 0 ||
	    file_open(&file, &sys->dir, path, &sys->lib->err) < 0)
		goto err_no_file;

	if (file_set(&file, sys->lib->buf.bytes, pos, &sys->lib->err) < 0 ||
	    file_close(&file, &sys->lib->err) < 0)
		goto err_file;

	return 0;
err_file:
	file_close(&file, &tmp);
err_no_file:
	err_where(&sys->lib->err, path);
	return -1;
}

int
ilk_srch_get(Rls_srch **srch_ref, uint64_t srch_cnt, Ilk_sys *sys)
{
	if ((*srch_ref = map_get(&sys->srchs, &srch_cnt, sizeof(srch_cnt))))
		return 0;

	return ilk_srch_load(srch_ref, srch_cnt, sys);
}

int
ilk_srch_load(Rls_srch **srch_ref, uint64_t srch_cnt, Ilk_sys *sys)
{
	Ilk_rule_path path;

	ilk_srch_path(path, srch_cnt);

	if (ilk_path_srch(srch_ref, path, sys) < 0)
		return -1;

	if (map_add(&sys->srchs, &srch_cnt, sizeof(srch_cnt),
		    *srch_ref, &sys->lib->stack) < 0) {
		err_cust(&sys->lib->err, "Failed to add srch");
		rls_recycle_srch(&sys->lib->rec, *srch_ref);
		*srch_ref = NULL;
		return -1;
	}

	return 0;
}

int
ilk_srch_unload(uint64_t srch_cnt, Ilk_sys *sys)
{
	Rls_srch *srch = map_get(&sys->srchs, &srch_cnt, sizeof(srch_cnt));

	if (!srch) {
		err_cust(&sys->lib->err, "Srch not found in unloading");
		return -1;
	}

	if (ilk_srch_save(sys, srch, srch_cnt) < 0)
		return -1;

	map_remove(&sys->srchs, &srch_cnt, sizeof(srch_cnt), &sys->lib->stack);
	rls_recycle_srch(&sys->lib->rec, srch);
	return 0;
}
